import BaseHTTPServer, select, signal, socket, SocketServer, urlparse
import re
from threading import Thread

__version__ = "0.1"

class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 
    def __init__(self, server_address, RequestHandlerClass, onReq):
        self.onReq=onReq
        BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass)
        
    def finish_request(self, request, client_address):
        self.onReq(request)
        self.RequestHandlerClass(request, client_address, self)

class PyddlerProxyServer(Thread):
    def __init__(self,oreq):
        self.running=False
        self.port=8000
        self.address=""
        Thread.__init__(self)
        self.proxy=ThreadingHTTPServer((self.address,self.port), ProxyHandler, oreq)
        
    def run(self):
        self.running=True
        print "PyddlerProxyServer::run"
        while self.running:
            self.proxy.handle_request()
        
    def stop(self):
        print "PyddlerProxyServer::stop"
        self.running=False
        
    def onRequest(self,req):
        pass
        #pass
        #print "REQUEST:" + req

class TimeoutError(Exception):
    pass

class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):

    server_version = "PyddlerHTTPProxy/" + __version__
    rx_hdr = re.compile("^([a-zA-Z\-0-9]+):\W+(.*)$")

    def do_CONNECT(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            try:
                connect_to(self.path, sock)
            except TimeoutError:
                self.send_error(504, "proxy has timed out")
                return
            self.log_request(200)
            self.wfile.write("%s %s %s\r\n" % (self.protocol_version, 200,
                                               "Connection established"))
            self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
            self.wfile.write("\r\n")
            self._read_write(sock, 300)
        finally:
            #print "bye"
            sock.close()
            self.connection.close()

    def do_GET(self):
        parsed = urlparse.urlparse(self.path, 'http')
        (scm, netloc, path, params, query, fragment)=parsed
        if scm != 'http' or fragment or not netloc:
            self.send_error(400, 'bad url %s' % self.path)
            return
        
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            try:
                connect_to(netloc, sock)
            except TimeoutError:
                self.send_error(504, "proxy has timed out")
                return
            self.log_request()
            str = '%s %s %s\r\n' % (
                self.command,
                urlparse.urlunparse(('', '', path, params, query, '')),
                self.request_version)
            sock.send(str)
            self.headers['Connection'] = 'close'
            del self.headers['Proxy-Connection']
            for (h, v) in self.headers.items():
                str = '%s: %s\r\n' % (h, v)
                print ">>", str.strip()
                #self.server.onReq(str.strip())
                sock.send(str)
            sock.send('\r\n')
            self._read_write(sock)
        finally:
            #print "bye"
            sock.close()
            self.connection.close()

    def _read_write(self, sock, max_idling=20):
        rfile = self.rfile
        if hasattr(rfile, '_rbuf'):     # on BeOS?
            data = rfile._rbuf
        else:
            if self.headers.has_key('Content-Length'):
                n = int(self.headers['Content-Length'])
                data = rfile.read(n)
            else:
                self.connection.setblocking(0)
                try: data = rfile.read()
                except IOError: data = ''
                self.connection.setblocking(1)
        rfile.close()
        if data:
            sock.send(data)
        iw = [self.connection, sock]
        count = 0
        self.headers_done = 0
        while 1:
            count += 1
            (ins, _, exs) = select.select(iw, [], iw, 3)
            if exs: break
            if ins:
                for i in ins:
                    if i is sock:
                        out = self.connection
                    else:
                        out = sock
                    data = i.recv(8192)
                    if data:
                        self.dump_headers(data)
                        out.send(data)
                        count = 0
            else:
                print "idle", count
            if count == max_idling: break

    def dump_headers(self, data):
        if self.headers_done:
            return
        for line in data.split("\n"):
            line = line.strip()
            if not line:
                self.headers_done = 1
                print
                return
            m = self.rx_hdr.match(line)
            if m:
                k, v = m.groups()
                print "<< %s: %s" % (k, v)
                
    do_HEAD = do_GET
    do_POST = do_GET
    do_PUT  = do_GET
    do_DELETE=do_GET
    
    
def SIGALRM_handler(sig, stack):
    raise TimeoutError()

signal.signal(signal.SIGALRM, SIGALRM_handler)


def connect_to(netloc, sock):           # throws TimeoutError
    i = netloc.find(':')
    if i >= 0:
        host, port = netloc[:i], int(netloc[i+1:])
    else:
        host, port = netloc, 80
    #print "connect to", host, port
    signal.alarm(10)
    try:
        sock.connect((host, port))
    finally:
        signal.alarm(0)